package simpleftp.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.Void;
import java.util.concurrent.Callable;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;

import simpleftp.client.control.Command;

/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files. 
 */
public class FTPServer implements Callable<Void> 
{
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
	private StringBuilder sb;
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	// TO BE COMPLETED - 1 LINE
	private DefaultMutableTreeNode fileHierarchy;
	
	/**
	 * default constructor
	 * @param s the client socket
	 */
	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				// TODO TO BE COMPLETED - 1 LINE default tree model?
				
				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));	
				//we add here default tree model
				fileHierarchy = new DefaultMutableTreeNode(/*f.getName()*/"..");
				createFileHierarchy(fileHierarchy,f);
				System.out.println("FTP Server : new client from " + socket.getInetAddress().getHostAddress());
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		} catch (IOException ioex) {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
	}

	/**
	 * method to handle the client request and call the corresponding method
	 */
	public Void call() {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			try {
				sendLine("error ");
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();

				switch (Command.valueOf(response.split(" ")[0])) {

				case PWD:
					sendLine("257 " + System.getProperty("user.dir"));
					break;

				case LIST:
					sb = new StringBuilder("200 ");
					/*String[] fileNames = f.list();
					for (String fileName : fileNames) {
						sb = sb.append(fileName + "|");
					}*/
					sb = sb.append(sendSerializedTree());
					if(sb.toString().contains("|!"))
						System.out.println("Error");
					if(sb.toString().contains("|!"))
						System.out.println("Error");
					String toSend = sb.toString().replace("\n", "|!").replace("\r", "!|");
					System.out.println("Size is : "+toSend.length());
					
					sendLine(toSend);
					break;

				case CWD:
					System.out.println("CWD");
					//change with path might be better
					/*if(response.substring(4).contains("..")){
						String path = System.getProperty("user.dir").toString();
						System.setProperty("user.dir", path+"../");
					} else {*/
						
					//}
					File testFile = new File(response.substring(4));
					if(testFile.exists()){
						System.setProperty("user.dir", response.substring(4));
						System.out.println("CWD new current directory : "
								+ System.getProperty("user.dir").toString());
					} else {
						System.out.println("Invalid directory");
					}
					f = new File(System.getProperty("user.dir"));
					fileHierarchy = new DefaultMutableTreeNode(/*f.getName()*/"..");
					createFileHierarchy(fileHierarchy,f);
					sendLine("250 ");
					
					break;

				case PASV:
					pasv();
					break;

				case STOR:
					stor();
					break;
					
				case RETR:
					retr();
					break;

				case QUIT:
					socket.close();
					System.out.println("QUIT :Goodbye");
					validations = 0;
					break;

				default:
					System.out.println("Unknown Command.");
					sendLine("error");
					break;

				}
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {

					e1.printStackTrace();
				}
				e.printStackTrace();
			}

		}
		Void Void = null;
		return Void;
	}
	
	// TODO TO BE COMPLETED - SEND SERIALIZABLE OBJECT OVER THE NETWORK - 5 LINES
	/**
	 * send the serialized tree of the file hierarchy on server side
	 * @return the serialized object in a string format
	 * @throws IOException
	 */
	public String sendSerializedTree() throws IOException{
		ByteArrayOutputStream data = new ByteArrayOutputStream();
		ObjectOutputStream tree = new ObjectOutputStream(data);
		tree.writeObject(fileHierarchy);
		tree.flush();
		return data.toString();
	}

	/**
     * Sends a raw command to the FTP server.
     */

	private void sendLine(String line) throws IOException {
		if (socket == null) {
			//throw new IOException("FTPServer is not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG) {
				System.out.println("> " + line);
			}
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	/**
	 * read command from the client
	 * @return the received line in a String format
	 * @throws IOException
	 */
	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG) {
			System.out.println("< " + line);
		}
		return line;
	}

	/**
	 * perform upload
	 */
	private void stor() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			InputStream is = dl2.getInputStream();
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1]
					+ " : File received");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	/**
	 * perform download
	 */
	private void retr() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			InputStream is = new FileInputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			OutputStream os = dl2.getOutputStream();
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("RETR " + response.split(" ")[1]
					+ " : File sent");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}

	/**
	 * set the transfer in passive mode
	 */
	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ InetAddress.getLocalHost().getHostAddress()
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	/**
	 * set debug mode
	 * @param b true for debug mode false otherwise
	 */
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
	
	/**
	 * Create tree for file hierarchy on server side
	 * @param parent the parent node
	 * @param obj the file used to construct the tree
	 */
	public void createFileHierarchy(DefaultMutableTreeNode parent,File obj){
		if(obj.isDirectory()){
			DefaultMutableTreeNode child = new DefaultMutableTreeNode(obj.getName());
			String[] fileList = obj.list();
			for(String name : fileList){
				File file = new File(obj.getAbsolutePath()+"/"+name);
				createFileHierarchy(child,file);
			}
			parent.add(child);
		} else {
			MutableTreeNode child = new DefaultMutableTreeNode(obj.getName());
			parent.add(child);
		}
	}
}
